// Uncle MiF

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "dict.h"
#import <Foundation/Foundation.h>

const char * dictSfx = ".dict";
const char * idxSfx  = ".idx";
// idxcSfx = ".idxc" ; note: not flexible, in code calculated as %sc from idxSfx

// stardict index entry
typedef struct 
{
 char* szWordUTF8;
 unsigned offset;
 unsigned length;
} DICT_ENTRY;

static unsigned _convertOrder = 1;// auto detect
// endian converter if needed
void convertOrder(unsigned *num)
{
	unsigned res;
	unsigned i;
	if (! _convertOrder)
		return;// nothing to do
	for (i = 0, res = 0; i < 4; i++)
	{
		res |= ( ( ( *num >> (i << 3) ) & 0xFF ) << ( ( 3 - i ) << 3 ) );
	}
	*num = res;
}

static int checkEndian = 1;
// universal edition ;-)
#define checkEndianOnce(val) \
{\
		if (checkEndian)\
		{\
			checkEndian = 0;\
			unsigned test = 0x01;\
			if (!*((unsigned char*)&test))\
				_convertOrder = 0;\
		}\
}

// replace \0 to \n into long descriptions
void lineBreaks(char * translated, unsigned length)
{
	unsigned i;
	for (i = 0; i < length; i++)
		if (!translated[i])
			translated[i] = '\n';
}

#ifndef STUPID

PArr indexes = NULL;// for all worked indexes

PArr newArr()
{
	PArr arr = (PArr)malloc(sizeof(TArr));
	memset(arr,0,sizeof(TArr));
	return arr;
}

PArr freeArr(PArr arr)
{
	if (!arr)
	{
#ifdef VERBOSE
		fprintf(stderr,"Trying to free NULL array detected.\n");
#endif
		return NULL;
	}
	if (arr->items)
	{
		free(arr->items);
		arr->items = NULL;
	}
	arr->size = 0;
	free(arr), arr = NULL;
	return NULL;
}

int reallocArrToCount(PArr arr,unsigned count)
{
	if (count <= arr->size)
		return 1;// already
	arr->size = count;
	arr->items = realloc(arr->items, sizeof(*(arr->items))*arr->size);
	return arr->items != NULL;
}

void pushArrItem(PArr arr, unsigned val)
{
	if (!arr)
		return;
	if (arr->count+1 > arr->size)
	{
		// force smart realloc
		if (!arr->size)
			arr->size = 1024;
		else
			arr->size <<= 1;// progressive
			arr->items = realloc(arr->items, sizeof(*(arr->items))*arr->size);
	}
	arr->items[arr->count] = val;
	arr->count++;
}

void freeArrItem(PArr arr,unsigned idx)
{
	if (arr && idx < arr->count)
	{
		if (idx+1 < arr->count)
			memmove(arr->items+idx,
											arr->items+idx+1,
											(arr->count-idx-1)*sizeof(*(arr->items)));
		arr->count--;
	}
}

unsigned getArrItem(PArr arr, unsigned idx, unsigned * isError)
{
	if (arr && idx < arr->count)
	{
		if (isError)
			*isError = 0;
		return arr->items[idx];
	}
	if (isError)
	{
		*isError = 1;
#ifdef VERBOSE
		fprintf(stderr,"Array index too big\n");
#endif
	}
	return 0;
}

unsigned getArrCount(PArr arr)
{
	if (!arr)
		return 0;
	return arr->count;
}

// index item specific
PIndexEntry newIndexEntry(const char* dict)
{
	PIndexEntry ie;
	ie = (PIndexEntry)malloc(sizeof(TIndexEntry));
	memset(ie,0,sizeof(TIndexEntry));
	ie->dict = strdup(dict);
	ie->idxArr = newArr();
	return ie;
}

PIndexEntry freeIndexEntry(PIndexEntry ie)
{
	if (!ie)
		return NULL;
	if (ie->dict)
	{
		free(ie->dict);
		ie->dict = NULL;
	}
	if (ie->idxArr)
		ie->idxArr = freeArr(ie->idxArr);
	free(ie), ie = NULL;
	return NULL;
}

PIndexEntry getIndexEntry(PArr indexes,const char* dict)
{
	unsigned i,isError,count = getArrCount(indexes);
	for (i = 0; i < count; i++)
	{
		unsigned val = getArrItem(indexes,i,&isError);
		if (!isError && val && !strcmp(dict,unpackIndexEntry(val)->dict))
			return unpackIndexEntry(val);
	}
	PIndexEntry ie = newIndexEntry(dict);// create new named index entry
	pushArrItem(indexes,packIndexEntry(ie));
	return ie;
}

void freeUsedIndexes()
{
	if (!indexes)
		return;// nothing to do
	unsigned i,isError,count = getArrCount(indexes);
	for (i = 0; i < count; i++)
	{
		unsigned val = getArrItem(indexes,i,&isError);
		if (!isError && val)
			freeIndexEntry(unpackIndexEntry(val));
	}
	indexes = freeArr(indexes);
}

// external global vals for more faster work and save stack on recursion
FILE * _efDict = NULL;
FILE * _efIdx = NULL;
const char * _eWord;
unsigned _eOffset = 0;
unsigned _eLength = 0;
unsigned _eLastIDX = -1;

char* getWordFromIndex(PArr idxArr,unsigned wIdx)
{
	_eLastIDX = -1;
	char wordBuf[1024];
	unsigned curCh = 0;
	unsigned isError, wOffset = getArrItem(idxArr,wIdx,&isError);
	if (isError)
	{
#ifdef VERBOSE
		fprintf(stderr,"Can't read word because index fetch error\n");
#endif
		return NULL;
	}
	fseek(_efIdx,wOffset,SEEK_SET);
	char bt;
	while (fread(&bt,1,1,_efIdx))
	{
		if (curCh < sizeof(wordBuf))
		{
			wordBuf[curCh++] = bt;
			if (!bt)
			{
				if (fread(&_eOffset,4,1,_efIdx) && fread(&_eLength,4,1,_efIdx))
				{
					convertOrder(&_eOffset);
					convertOrder(&_eLength);
					_eLastIDX = wIdx;
					return strdup(wordBuf);
				}
				else
				{
#ifdef VERBOSE
					fprintf(stderr,"Offset/length not readed\n");
#endif
					break;
				}
			}
		}
		else
		{
#ifdef VERBOSE
			fprintf(stderr,"Buffer overflow for index word %u\n",wIdx);
#endif
			break;
		}
	}
#ifdef VERBOSE
	fprintf(stderr,"End of lookup or index file at pos %u/0x%X, wOffs is: 0x%X, bt"
																	" is: 0x%X, curCh is: 0x%X\n",
									ftell(_efIdx),ftell(_efIdx),wOffset,(unsigned)bt,(unsigned)curCh);
#endif
	return NULL;
}

char* getWordDescFromDictForLastIndex()
{
	char* translated = NULL;
	if (_eLength)
	{
		translated = (char*)malloc(_eLength+1);
		if (translated)
		{
			translated[_eLength] = 0;
			if (!fseek(_efDict, _eOffset, SEEK_SET) && 
																			fread(translated,_eLength,1,_efDict))
			{
				lineBreaks(translated,_eLength);
			}
			else
			{
				free(translated);
				translated = NULL;
			}
		}
	}
	return translated;
}

int _eSuggestionsMode = 0; 
unsigned searchWordIndex(PArr idxArr,unsigned begin,unsigned end,
																																																						unsigned * found)
{
	*found = 0;
	if (!idxArr || end<begin)
		return 0;
	unsigned diff = (end - begin) / 2;
	unsigned mid = begin + diff;
 char * midWord = getWordFromIndex(idxArr,mid);
#ifdef VERBOSE
	fprintf(stderr,"-> %08X %08X %08X (%08X) %s\n",begin,mid,end,diff,midWord);
#endif
	if (!midWord)
	{
#ifdef VERBOSE
		fprintf(stderr,
										"Error reading word from index file (index num is %u [%u;%u])\n",
										mid,begin,end);
#endif
		return 0;
	}
	int cmp = ( _eSuggestionsMode ? 
													strncasecmp(midWord,_eWord,strlen(_eWord))
													:
													strcasecmp(midWord,_eWord));
	free(midWord), midWord = NULL;
	if (cmp < 0)
	{
		// right piece
		if (!diff)
			return 0;
		return searchWordIndex(idxArr,mid,end,found);
	}
	else if (cmp)// cmp > 0
	{
		// left piece
		if (!diff)
			return 0;
		return searchWordIndex(idxArr,begin,mid,found);
	}
	// bingo!
	*found = 1;
	return mid;
}

char* quickWordSearch(PArr idxArr,FILE* fIdx,FILE* fDict,unsigned lenFIdx,
																																									unsigned lenFDict,const char* word)
{
	unsigned found;
	// setup globals (refactoring need for multithreading)
	_efIdx = fIdx;
	_efDict = fDict;
	_eWord = word;
	// call recursion b/search
	searchWordIndex(idxArr,0,getArrCount(idxArr),&found);
	if (!found)
		return NULL;
	return getWordDescFromDictForLastIndex();
}

#endif

char* getWord(const char* dict, const char* word,int suggestionsMode)
{
	char * translated = NULL;// result
	if (!dict[0] || !word[0])
		return translated;
#ifdef STUPID
	char wordBuf[1024];
#endif
	char *nDict, *nIdx;
	unsigned lDict = strlen(dict) + strlen(dictSfx) + 1,
		lIdx = strlen(dict) + strlen(idxSfx) + 1;
	FILE *fDict, *fIdx;
	nDict = malloc(lDict);
	sprintf(nDict,"%s%s", dict, dictSfx);
	nIdx = malloc(lIdx);
	sprintf(nIdx,"%s%s", dict, idxSfx);
	fDict = fopen(nDict,"r");
	if (fDict)
	{
		fIdx = fopen(nIdx,"r");
		if (fIdx)
		{
			DICT_ENTRY dicEnt;
			char bt = 0;
#ifdef STUPID
			unsigned curCh = 0;
#endif
			unsigned lenFIdx, lenFDict;
			
			fseek(fIdx,0,SEEK_END);
			lenFIdx = ftell(fIdx);
			fseek(fIdx,0,SEEK_SET);
			
			fseek(fDict,0,SEEK_END);
			lenFDict = ftell(fDict);
			fseek(fDict,0,SEEK_SET);
#ifdef STUPID
			///////////////////////////////////////////////////////////////////////
			// first of all (for parsing test):
			// stupid idea release: from first to last word index scan
			// but it's works very fast on my PowerBook G4 ;-)
			while (fread(&bt,1,1,fIdx))
			{
				if (curCh < sizeof(wordBuf))
				{
					wordBuf[curCh++] = bt;
					if (!bt)
					{
						curCh = 0;
						dicEnt.szWordUTF8 = &wordBuf[0];
						if (fread(&dicEnt.offset,4,1,fIdx) && fread(&dicEnt.length,4,1,fIdx))
						{
							// converting to Intel bytes order if needed (!)
							checkEndianOnce(dicEnt.length);
							convertOrder(&dicEnt.offset);
							convertOrder(&dicEnt.length);
							if (dicEnt.offset < lenFDict && dicEnt.length < lenFDict)
							{
								// comparing word
								int cmp = (suggestionsMode ? 
																			strncasecmp(word, dictEnt.szWordUTF8,strlen(word)) 
																			: 
																			strcasecmp( word, dicEnt.szWordUTF8));
								if (!cmp)
								{
									// equal (!)
									// cut translation from dictionay
									if (dicEnt.length)
									{
										translated = malloc(dicEnt.length+1);
										if (translated)
										{
											translated[dicEnt.length] = 0;
											if (!fseek(fDict, dicEnt.offset, SEEK_SET) &&
																													fread(translated,dicEnt.length,1,fDict))
											{
												lineBreaks(translated,dicEnt.length);
											}
											else
											{
												free(translated), translated = NULL;
											}
										}
									}
									break;
								}
								else
									if (cmp < 0)
									{
										// not found now
										break;
									}
								
							}
							else
							{
#ifdef VERBOSE
								fprintf(stderr,
																"Record offset or length out of file %s near pos 0x%lX.\n",
																nIdx,ftell(fIdx));
#endif
								exit(4);
							}
						}
						else
						{
#ifdef VERBOSE
							fprintf(stderr,
															"Unexpected record termination while parsing index file"
															" %s near pos 0x%lX.\n",
															nIdx,ftell(fIdx));
#endif
							exit(3);
						}
					}
				}
				else
				{
#ifdef VERBOSE
					fprintf(stderr,
													"Word buffer overflow while parsing index file"
													" %s near pos 0x%lX.\n",
													nIdx,ftell(fIdx));
#endif
					exit(2);
				}
			}
#else
			//////////////////////////////////////////
			// Quick word search
			char pbt;
			if (!indexes)
				indexes = newArr();// root for all indexes
			PIndexEntry ie = getIndexEntry(indexes,dict);
			if (!getArrCount(ie->idxArr))
			{
				char * nIdxc = malloc(strlen(nIdx)+1+1);
				sprintf(nIdxc,"%sc",nIdx);
				FILE * fIdxCache = fopen(nIdxc,"r");
				if (fIdxCache)
				{
					// load from disk
					int done = loadIndexCacheIndirect(ie,fIdxCache);
					fclose(fIdxCache);
					if (!done)
					{
						unlink(nIdxc);
						fprintf(stderr,"index cache is out to date for %s\n",nIdx);
						goto forceBuildingIndex;
					}
				}
				else
				{
forceBuildingIndex:
					// first iteration: build idx offset array once for current dictionary
					/// *****************
					while (pbt=bt,fread(&bt,1,1,fIdx))
					{
						if (!pbt)
							pushArrItem(ie->idxArr,ftell(fIdx)-1);
						if (!bt)
						{
							if (fread(&dicEnt.offset,4,1,fIdx) && fread(&dicEnt.length,4,1,fIdx))
							{
								// converting to Intel bytes order if needed (!)
								checkEndianOnce(dicEnt.length);
							}
						}
					}
					/// *****************
					fIdxCache = fopen(nIdxc,"w");
					if (fIdxCache)
					{
						// save to disk
						saveIndexCacheIndirect(ie,fIdxCache);
						fclose(fIdxCache);
					}
					else
						NSLog(@"can't save index cache at file %s",nIdxc);
				}
#ifdef VERBOSE
				NSLog(@"Index loaded. Items count: %u, final array size: %u",
																																			getArrCount(ie->idxArr),ie->idxArr->size);
#endif
			}
			_eSuggestionsMode = suggestionsMode;
			translated = quickWordSearch(ie->idxArr,fIdx,fDict,lenFIdx,lenFDict,word);
#endif
			if (!suggestionsMode)
			{
				fclose(fIdx); _efIdx = NULL;
			}
		}
		else
		{
			fprintf(stderr,"Failed: Index file %s not found.\n",nIdx);
		}
		if (!suggestionsMode)
		{
			fclose(fDict); _efDict = NULL;
		}
	}
	else
	{
		fprintf(stderr,"Failed: Dictionary file %s not found\n",nDict);
	}
	if (nDict)
		free(nDict), nDict = NULL;
	if (nIdx)
		free(nIdx), nIdx = NULL;
	return translated;
}

unsigned sumCRC(unsigned * items,unsigned count)
{
	unsigned sum = 0x01020304;
	unsigned i = 0;
	while (count--)
	{
		sum += items[i];
		i++;
	}
	return sum;
}

int loadIndexCacheIndirect(PIndexEntry ie,FILE * fIdxCache)
{
	checkEndianOnce(dummy);
	unsigned size;
	fseek(fIdxCache,0,SEEK_END);
 size = ftell(fIdxCache);
	if (!size)
	{
		fprintf(stderr,"strange: cache exists but empty\n");
		return 0;// empty cache
	}
	fseek(fIdxCache,0,SEEK_SET);
	PArr idxArr = ie->idxArr;
	unsigned count = size/sizeof(*(idxArr->items));
	if (count*sizeof(*(idxArr->items)) != size || count < 2)
	{
		fprintf(stderr,"index cache damaged\n");
		return 0;
	}
	if (!idxArr)
		idxArr = ie->idxArr = newArr();
	if (!reallocArrToCount(idxArr,count))
	{
		fprintf(stderr,"can't prepare index array\n");
		return 0;
	}
	size_t res = fread(idxArr->items,size,1,fIdxCache);
	if (res)
	{
		unsigned realCount = count - 1;
		unsigned checkSum = *(idxArr->items + realCount);
		if (sumCRC(idxArr->items,realCount) == checkSum)
		{
			idxArr->count = realCount;
			return 1;
		}
		fprintf(stderr,"check sum error\n");
		return 0;
	}
	fprintf(stderr,"index cache reading error\n");
	return 0;
}

void saveIndexCacheIndirect(PIndexEntry ie,FILE * fIdxCache)
{
	fseek(fIdxCache,0,SEEK_END);
	unsigned size;
	size = ftell(fIdxCache);
	if (size)
		return;// cache exists
	fseek(fIdxCache,0,SEEK_SET);
	PArr idxArr = ie->idxArr;
	size = idxArr->count * sizeof(*(idxArr->items));
	fwrite(idxArr->items,size,1,fIdxCache);
	unsigned checkSum = sumCRC(idxArr->items,idxArr->count);
	fwrite(&checkSum,sizeof(checkSum),1,fIdxCache);
}

void closeSuggestionsSession()
{
	if (_efIdx)
	{
		fclose(_efIdx); _efIdx = NULL;
	}
	if (_efDict)
	{
		fclose(_efDict); _efDict = NULL;
	}		
}

unsigned getSuggestionBegin(const char* dict,const char* wBegin)
{
	// trace back to head
	char * translated = getWord(dict,wBegin,1);
	if (translated)
	{
		free(translated), translated = NULL;
		if (_eLastIDX != -1)
		{
			unsigned start = _eLastIDX;
			PIndexEntry pe = getIndexEntry(indexes,dict);
			while (start)
			{
				char * word = getWordFromIndex(pe->idxArr,start-1);
				if (word)
				{
					if (!strncasecmp(word,wBegin,strlen(wBegin)))
					{
						start--;
						free(word), word = NULL;
						continue;
					}
					free(word), word = NULL;
				}
				break;
			}
			return start;
		}
	}
	closeSuggestionsSession();
	return -1;
}

unsigned getNextSuggestion(PArr idxArr,const char* wBegin,unsigned sID)
{
	if (sID < getArrCount(idxArr))
	{
		char * word = getWordFromIndex(idxArr,sID+1);
		if (word)
		{
			if (!strncasecmp(word,wBegin,strlen(wBegin)))
			{
				free(word), word = NULL;
				return sID+1;
			}
			free(word), word = NULL;
		}
	}
	return -1;
}

// check for duplicates
int suggestionAlreadyExists(char ** suggestions,char * suggestion)
{
	while (*suggestions)
	{
		if (!strcasecmp(*suggestions,suggestion))
			return 1;
		suggestions++;
	}
	return 0;
}

char ** getSuggestions(const char* wBegin)
{
	if (!indexes)
		return NULL;
	unsigned activeDicts = getArrCount(indexes);
	if (!activeDicts)
		return NULL;
	unsigned size = 1024, count = 0;
	char ** suggestions = malloc(sizeof(char*)*size);
	memset(suggestions,0,sizeof(char*)*size);
	// foreach active dicts
	unsigned i;
	for (i = 0; i < activeDicts; i++)
	{
		PIndexEntry ie = unpackIndexEntry(getArrItem(indexes,i,NULL));
		char * sDict = ie->dict;
		PArr idxArr = ie->idxArr;
		unsigned sID = getSuggestionBegin(sDict,wBegin);
		if (sID != -1)
		{
			do
			{
				if (count+1 >= size)
				{
					size <<= 1;
					suggestions = realloc(suggestions,sizeof(char*)*size);
				}
				char * suggestion = getWordFromIndex(idxArr,sID);
				if (suggestion && !suggestionAlreadyExists(suggestions,suggestion))
				{
					suggestions[count] = suggestion;// push new suggestion
					count++;
					suggestions[count] = NULL;
				}
			}
			while ((sID = getNextSuggestion(idxArr,wBegin,sID)) != -1);
			closeSuggestionsSession();
		}
	}
	if (!count)
	{
		if (suggestions)
			free(suggestions), suggestions = NULL;
		return NULL;
	}
	return suggestions;
}

void freeUnusedDict(const char* dict)
{
	if (!indexes)
		return;
	unsigned activeDicts = getArrCount(indexes);
	if (!activeDicts)
		return;
	// foreach active dicts
	unsigned i;
	for (i = 0; i < activeDicts; i++)
	{
		PIndexEntry ie = unpackIndexEntry(getArrItem(indexes,i,NULL));
		if (ie && !strcasecmp(ie->dict,dict))
		{
			freeIndexEntry(ie);
			freeArrItem(indexes,i);
			break;// or for continue lookup: activeDicts--,i--;
		}
	}
}

char ** getIndexedDicts()
{
	if (!indexes)
		return NULL;
	unsigned activeDicts = getArrCount(indexes);
	if (!activeDicts)
		return NULL;
	unsigned size = 1024, count = 0;
	char ** dicts = malloc(sizeof(char*)*size);
	memset(dicts,0,sizeof(char*)*size);
	// foreach active dicts
	unsigned i;
	for (i = 0; i < activeDicts; i++)
	{
		PIndexEntry ie = unpackIndexEntry(getArrItem(indexes,i,NULL));
		char * sDict = ie->dict;
		if (!sDict)
			continue;
		if (count+1 >= size)
		{
			size <<= 1;
			dicts = realloc(dicts,sizeof(char*)*size);
		}
		dicts[count] = strdup(sDict);// push new suggestion
		count++;
		dicts[count] = NULL;
	}
	if (!count)
	{
		if (dicts)
			free(dicts), dicts = NULL;
		return NULL;
	}
	return dicts;
}

void freePCharList(char ** list)
{
	if (list)
	{
		char ** eye = list;
		while (*eye)
		{
			free(*eye), *eye = NULL;
			eye++;
		}
		free(list), list = NULL;
	}
}
